package com.dog.modules.iot.mqtt.consumer.dispatcher;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.alibaba.fastjson2.util.ParameterizedTypeImpl;
import com.dog.modules.iot.mqtt.codec.BodyDecoder;
import com.dog.modules.iot.mqtt.consumer.annotation.CmdMapping;
import com.dog.modules.iot.mqtt.consumer.bound.InboundCommand;
import com.dog.modules.iot.mqtt.consumer.enums.InCommandType;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 */
@Slf4j
@Component
public class CmdProcessor implements ICmdProcessor, InitializingBean {

    @Value("${spring.mqtt.client.decrypt:true}")
    private boolean isDecrypt;

    /**
     * 使用spring提供的方法，解析方法参数名
     */
    private final ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();

    private static final Map<InCommandType, CmdExecutor> CMD_HANDLERS = new HashMap<>();

    @Autowired
    private BodyDecoder inBodyParser;

    @Autowired
    private ApplicationContext applicationContext;

    @Override
    public void initHandler() {
        // 从ioc容器中找出所有的带@CmdController的bean
        Map<String, Object> cmdControllerMap =
                applicationContext.getBeansWithAnnotation(com.dog.modules.iot.mqtt.consumer.annotation.CmdDispatcher.class);
        for (Object cmdController : cmdControllerMap.values()) {
            Class<?> clazz = cmdController.getClass();
            Method[] methods = clazz.getDeclaredMethods();
            for (Method method : methods) {
                CmdMapping cmdMapping = method.getAnnotation(CmdMapping.class);
                if (cmdMapping != null) {
                    InCommandType cmd = cmdMapping.cmd();
                    CmdExecutor cmdExecutor = CMD_HANDLERS.get(cmd);
                    if (cmdExecutor != null) {
                        throw new RuntimeException(String.format("cmd[%d] duplicated", cmd.getCmd()));
                    }
                    cmdExecutor = new CmdExecutor(method, method.getGenericParameterTypes(), cmdController);
                    CMD_HANDLERS.put(cmd, cmdExecutor);
                }
            }
        }
    }

    @Override
    public void dispatch(String content) {
        InboundCommand cmd = JSONObject.parseObject(content, InboundCommand.class);
        InCommandType inCommandType = InCommandType.valueOf(cmd.getHeader().getCmd());
        if (inCommandType == null) {
            log.warn("unknown cmd:{}", cmd.getHeader().getCmd());
            return;
        }
        // 获取cmd对应的handler
        CmdExecutor cmdExecutor = CMD_HANDLERS.get(inCommandType);
        Object cmdController = cmdExecutor.getHandler();
        Method method = cmdExecutor.getMethod();
        try {
            Object[] params = null;
            switch (inCommandType) {
                case CONNECT:
                case DISCONNECT:
                    //系统消息
                    params = new Object[1];
                    // 绑定名称为deviceCode的参数
                    params[0] = cmd.getHeader().getDeviceCode();
                    log.info("processing msg for {} {}", cmd.getHeader().getDeviceCode(), inCommandType);
                    break;
                default:
                    //业务消息
                    boolean isEncode = isDecrypt;
                    params = decodeAndBindParams(inCommandType, cmdExecutor, cmd.getData(), cmd.getHeader().getDeviceCode(), isEncode);
                    break;
            }
            method.invoke(cmdController, params);
        } catch (Exception e) {
            log.error("processing msg for {} {} failed, id:{}", cmd.getHeader().getDeviceCode(), inCommandType, cmd.getHeader().getMqttUuid());
            e.printStackTrace();
        }
    }

    /**
     * 将参数转化为CmdMapping注解的方法的实参
     */
    private Object[] decodeAndBindParams(InCommandType cmd, CmdExecutor cmdExecutor, Object encodeData, String deviceCode, boolean isEncode) {
        // 获取方法所有的参数名
        String[] parameterNames = parameterNameDiscoverer.getParameterNames(cmdExecutor.getMethod());
        Object[] params = new Object[parameterNames == null ? 0 : parameterNames.length];
        Type[] methodParams = cmdExecutor.getParams();
        Object data = null;
        if (parameterNames != null) {
            for (int i = 0; i < parameterNames.length; i++) {
                Type paramType = methodParams[i];

                if ("deviceCode".equals(parameterNames[i])) {
                    // 绑定名称为deviceCode的参数
                    params[i] = deviceCode;
                } else if (paramType instanceof ParameterizedTypeImpl &&
                        ((ParameterizedTypeImpl) paramType).getClass().isAssignableFrom(List.class)) {
                    // 绑定类型为List的参数
                    ParameterizedTypeImpl type = (ParameterizedTypeImpl) paramType;
                    Type[] types = type.getActualTypeArguments();
                    if (encodeData != null) {
                        data = params[i] = inBodyParser.decodeDataWithArray(deviceCode, encodeData, isEncode, types);
                    }
                } else {
                    // 绑定普通对象参数
                    if (encodeData != null) {
                        data = params[i] = inBodyParser.decodeDataWithSimpleObject(deviceCode, encodeData, isEncode, paramType);
                    }
                }
            }
        }
        log.info("processing msg for {} {}: {} ", deviceCode, cmd, JSON.toJSONString(data));
        return params;
    }

    @Override
    public void afterPropertiesSet() {
        initHandler();
    }
}
